package org.hawk.gwt.ppc.scanner.utils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.RandomAccess;

import junit.framework.TestCase;

import org.hawk.gwt.ppc.javacc.JavaParserTreeConstants;
import org.hawk.gwt.ppc.javacc.JavaScanner;
import org.hawk.gwt.ppc.javacc.ParseException;
import org.hawk.gwt.ppc.scanner.Processor;
import org.hawk.gwt.ppc.scanner.Token;
import org.hawk.gwt.ppc.scanner.TokenChain;
import org.hawk.gwt.ppc.utils.Name;

public class JavaClassDescriptorTestCase extends TestCase {
	private static final String MOCK1 = "org.hawk.gwt.ppc.scanner.mock.Mock1";
	private static final String MOCK2 = "org.hawk.gwt.ppc.scanner.mock.Mock2";
	private static final String MOCK3 = "org.hawk.gwt.ppc.scanner.mock.Mock3";
	private static final String MOCK3X = "org.hawk.gwt.ppc.reflect.mock.Mock3";
	private static final String MOCK4 = "org.hawk.gwt.ppc.reflect.mock.ArraysMock";
	private static final String MOCK5 = "org.hawk.gwt.ppc.reflect.mock.ComplexMock";
	private static final String MOCK6 = "org.hawk.gwt.ppc.reflect.mock.GenericClassMock";
	
	public void testCreate() throws IOException, ParseException{
		JavaClassDescriptor clazz = JavaClassDescriptorUtils
				.getClassDescriptor(MOCK1);
		assertEquals(Arrays.asList(new JavaVariable[]{
				new JavaVariable("class","java.lang.Class",0,null),
				new JavaVariable("VAR","int",0,null),
				new JavaVariable("zzz","java.lang.String",0,null),
				new JavaVariable("clazz","java.lang.Class<?>",0,null),
				new JavaVariable("bitset","java.util.BitSet",0,null),
				new JavaVariable("inner","java.lang.String",0,null),
				new JavaVariable("str","java.lang.Class<?>",0,null)
				}),
			clazz.getScope(93).getVariables(93));
		assertEquals(Arrays.asList(new JavaVariable[]{
				new JavaVariable("class","java.lang.Class",0,null),
				new JavaVariable("VAR","int",0,null),
				new JavaVariable("zzz","java.lang.String",0,null),
				new JavaVariable("clazz","java.lang.Class<?>",0,null),
				new JavaVariable("bitset","java.util.BitSet",0,null),
				new JavaVariable("name","java.lang.String",0,null)
				}),
			clazz.getScope(150).getVariables(150));
	}
	
	public void testGetVariableType()
			throws IOException, ParseException{
		JavaClassDescriptor clazz = JavaClassDescriptorUtils
			.getClassDescriptor(MOCK1);
		assertEquals("java.lang.Class<?>",
			clazz.getScope(88).getVariable(93,"str").getTypeName());
		assertEquals("java.lang.Class<?>",
			clazz.getScope(88).getVariable(93,"clazz").getTypeName());
		assertEquals("java.util.BitSet",
				clazz.getScope(129).getVariable(129,"bitset").getTypeName());
		assertEquals("int",
				clazz.getScope(129).getVariable(129,"zzz").getTypeName());
	}
	
	@SuppressWarnings("unchecked")
	public void testGetMethods()
		throws IOException, ParseException{
		JavaClassDescriptor clazz = JavaClassDescriptorUtils
			.getClassDescriptor(MOCK1);
		assertEquals(Arrays.asList(new JavaMethod[]{
				new JavaMethod("doSomething", null, 0,
						new LinkedList<JavaVariable>(),"void", false),
				new JavaMethod("getStringClass", null, 0,
						new LinkedList<JavaVariable>(),"java.lang.Class<String>", false),
				new JavaMethod("getStringClass", null, 0,
						Arrays.asList(new JavaVariable[]{
								new JavaVariable("", "java.lang.String", 0, null)
				}),"java.lang.Class<String>", false),
				new JavaMethod("getStringClass", null, 0,
						Arrays.asList(new JavaVariable[]{
								new JavaVariable("", "java.lang.String", 0, null),
								new JavaVariable("", "int", 0, null)
				}),"java.lang.Class<String>", false),
				new JavaMethod("getClass", null, 0, Collections.EMPTY_LIST,"java.lang.Class", false),
				new JavaMethod("equals",null,0,
						Arrays.asList(new JavaVariable[]{
							new JavaVariable("", "java.lang.Object", 0, null)
				}),"boolean", false),
				new JavaMethod("toString", null, 0, Collections.EMPTY_LIST, "java.lang.String", false),
				new JavaMethod("hashCode", null, 0, Collections.EMPTY_LIST, "int", false)
			}),
			clazz.getCompilationUnit().getMethods());
		
		JavaClassDescriptor clazzGeneric = JavaClassDescriptorUtils
				.getClassDescriptor(MOCK6);
			assertEquals(Arrays.asList(new JavaMethod[] {
					new JavaMethod("get", null, 0,
							Arrays.asList(new JavaVariable[]{
									new JavaVariable("", "int", 0, null)
							}),"java.util.Map", false),
					new JavaMethod("size", null, 0, Collections.EMPTY_LIST, "int", false),
					new JavaMethod("getT", null, 0,
							Arrays.asList(new JavaVariable[]{
									new JavaVariable("", "java.lang.CharSequence", 0, null),
									new JavaVariable("", "java.lang.Object", 0, null),
									new JavaVariable("", "java.lang.Object", 0, null),
							}), "java.lang.Object", false)
			}), clazzGeneric.getCompilationUnit().getMethods().subList(0, 3));
	}
	
	public void testGetMethodReturnType()
		throws IOException, ParseException{
		JavaClassDescriptor clazz = JavaClassDescriptorUtils.getClassDescriptor(MOCK1);
		assertEquals("java.lang.Class", clazz
				.getCompilationUnit().getMethod("getStringClass",
						"java.lang.String").getReturnType());
	}
	
	public void testSimpleExpressions() throws IOException, ParseException{
		JavaScanner scanner = new JavaScanner();
		scanner.addProcessor(JavaParserTreeConstants.JJTCOMPILATIONUNIT, new Processor() {
			public void visitStart(TokenChain<? extends Token> iterator, int kind) {}
			
			public void visitEnd(TokenChain<? extends Token> iterator, int kind) {
				try {
					assertEquals(String.class.getName(), 
						JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
							iterator, MOCK2, 21, 35));
					assertEquals(Class.class.getName(), 
						JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
							iterator, MOCK2, 36, 41));
					assertEquals(byte[].class.getName(), 
						JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
							iterator, MOCK2, 42, 61));
					assertEquals(Method.class.getName(), 
						JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
							iterator, MOCK2, 62, 94));
					assertEquals(Class.class.getName(), 
						JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
							iterator, MOCK2, 95, 118));
					assertEquals(Class.class.getName(), 
							JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
								iterator, MOCK2, 119, 151));
				}
				catch(IOException ex){
					throw new RuntimeException(ex);
				}
				catch(ParseException ex){
					throw new RuntimeException(ex);
				}
			}
		});
		scanner.startScan(new FileInputStream("./src/"+Name.javaToFs(MOCK2)), null);
	}
	
	public void testComplexExpressions() throws FileNotFoundException, ParseException {
		JavaScanner scanner = new JavaScanner();
		scanner.addProcessor(JavaParserTreeConstants.JJTCOMPILATIONUNIT, new Processor() {
			public void visitStart(TokenChain<? extends Token> iterator, int kind) {}
			
			public void visitEnd(TokenChain<? extends Token> iterator, int kind) {
				try {
					assertEquals(int.class.getName(), 
						JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
							iterator, MOCK5, 54, 67));
					assertEquals(Constructor.class.getName(), 
							JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
								iterator, MOCK5, 54, 64));
					assertEquals(Class.class.getName(), 
							JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
								iterator, MOCK5, 80, 83));
					assertEquals(Class.class.getName(), 
							JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
								iterator, MOCK5, 103, 106));
					assertEquals(Object.class.getName(), 
							JavaClassDescriptorUtils.evaluateReturnTypeOfExpression(
								iterator, MOCK5, 116, 145));
				}
				catch(IOException ex){
					throw new RuntimeException(ex);
				}
				catch(ParseException ex){
					throw new RuntimeException(ex);
				}
			}
		});
		scanner.startScan(new FileInputStream("./src/"+Name.javaToFs(MOCK5)), null);
	}
	
	public void testArrayTypes() throws IOException, ParseException {
		JavaClassDescriptor clazz = JavaClassDescriptorUtils.getClassDescriptor(MOCK4);
		JavaVariable var = clazz.getCompilationUnit().getVariable(37, "FIELD");
		assertEquals("FIELD", var.getName());
		assertEquals("java.lang.String[]", var.getTypeName());
		JavaMethod method = clazz.getCompilationUnit().getMethod("getMultiArray");
		assertEquals("getMultiArray", method.getName());
		assertEquals("java.lang.String[][][][]", method.getReturnType());
	}
	
	public void testAnonimousClass() throws IOException, ParseException{
		JavaClassDescriptor clazz = JavaClassDescriptorUtils.getClassDescriptor(MOCK3);
		assertFalse(clazz.getCompilationUnit().getChildren().isEmpty());
		JavaMethod m = (JavaMethod) clazz.getCompilationUnit().getChildren().get(0);
		assertNotNull(m);
		assertFalse(m.getChildren().isEmpty());
		assertEquals("void",m.getReturnType());
		JavaScope mScope = m.getChildren().get(0);
		assertNotNull(mScope);
		assertFalse(mScope.getChildren().isEmpty());
		JavaScope anonimousClass = mScope.getChildren().get(0);
		assertNotNull(anonimousClass);
		assertTrue(anonimousClass instanceof JavaTypeScope);
		assertEquals(1,anonimousClass.getChildren().size());
		assertTrue(anonimousClass.getChildren().get(0) instanceof JavaMethod);
		JavaTypeScope type = (JavaTypeScope)anonimousClass;
		assertEquals(null, type.getClassName());
		assertEquals(Object.class.getName(), type.getSuperClass().getClassName());
		assertNotNull(type.getImplementedInterfaces());
		assertEquals(1, type.getImplementedInterfaces().size());
		assertEquals("com.google.gwt.event.dom.client.ClickHandler",
				type.resolveType(type.getImplementedInterfaces().get(0).getClassName()));
	}
	
	public void testGenericTypeParameters() throws IOException, ParseException {
		JavaClassDescriptor clazz = JavaClassDescriptorUtils.getClassDescriptor(MOCK6);
		List<JavaTypeVariable> result = clazz.getCompilationUnit().getTypeVariables();
		assertEquals(3, result.size());
		assertEquals("X1", result.get(0).getName());
		assertEquals("X2", result.get(1).getName());
		assertEquals("X3", result.get(2).getName());
		
		assertEquals(2, result.get(0).getBounds().size());
		assertEquals(0, result.get(1).getBounds().size());
		assertEquals(0, result.get(2).getBounds().size());
		
		assertEquals(CharSequence.class.getName(), result.get(0).getBounds().get(0).getResolvedClassName());
		assertEquals(RandomAccess.class.getName(), result.get(0).getBounds().get(1).getResolvedClassName());
	}
	
	public void testGetGenericSuperclass() throws IOException, ParseException {
		assertNull(JavaClassDescriptorUtils.getClassDescriptor(MOCK5).getCompilationUnit().getGenericSuperclass());
		assertNull(JavaClassDescriptorUtils.getClassDescriptor(MOCK3X).getCompilationUnit().getGenericSuperclass());
		
		JavaClassDescriptor clazz = JavaClassDescriptorUtils.getClassDescriptor(MOCK6);
		//AbstractList<Map<? extends Number, Map<? super Number, Integer[]>>>
		JavaGenericTypeScope superclass = clazz.getCompilationUnit().getGenericSuperclass();
		assertNotNull(superclass);
		assertTrue(superclass instanceof JavaParameterizedGenericTypeScope);
		JavaParameterizedGenericTypeScope pt = (JavaParameterizedGenericTypeScope)superclass;
		assertEquals(1, pt.getTypeParameters().size());
		assertEquals("java.util.AbstractList", pt.getResolvedClassName());
		
		JavaTypeScope param = pt.getTypeParameters().get(0);
		assertNotNull(param);
		assertEquals("java.util.Map", param.getResolvedClassName());
		assertTrue(param instanceof JavaParameterizedGenericTypeScope);
		
		pt = (JavaParameterizedGenericTypeScope) param;
		assertEquals(2, pt.getTypeParameters().size());
		
		JavaTypeScope p1 = pt.getTypeParameters().get(0);
		assertNotNull(p1);
		assertTrue(p1 instanceof JavaWildcardGenericTypeScope);
		JavaWildcardGenericTypeScope p1casted = (JavaWildcardGenericTypeScope)p1; 
		assertEquals(1, p1casted.getUpperBounds().size());
		assertEquals("java.lang.Number", p1casted.getUpperBounds().get(0).getResolvedClassName());
		assertNull(p1casted.getLowerBound());
		
		JavaTypeScope p2 = pt.getTypeParameters().get(1);
		assertNotNull(p2);
		assertEquals("java.util.Map", p2.getResolvedClassName());
		assertTrue(param instanceof JavaParameterizedGenericTypeScope);
		pt = (JavaParameterizedGenericTypeScope) p2;
		assertEquals(2, pt.getTypeParameters().size());
		
		p1 = pt.getTypeParameters().get(0);
		assertNotNull(p1);
		assertTrue(p1 instanceof JavaWildcardGenericTypeScope);
		p1casted = (JavaWildcardGenericTypeScope)p1; 
		assertEquals(1, p1casted.getUpperBounds().size());
		assertEquals("java.lang.Object", p1casted.getUpperBounds().get(0).getResolvedClassName());
		assertEquals("java.lang.Number", p1casted.getLowerBound().getResolvedClassName());
		
		p2 = pt.getTypeParameters().get(1);
		assertNotNull(p2);
		assertTrue(p2 instanceof JavaArrayGenericTypeScope);
		JavaArrayGenericTypeScope array = (JavaArrayGenericTypeScope) p2;
		assertEquals("java.lang.Class", array.getComponentType().getResolvedClassName());
		assertTrue(array.getComponentType() instanceof JavaParameterizedGenericTypeScope);
		
		JavaParameterizedGenericTypeScope component = (JavaParameterizedGenericTypeScope)array.getComponentType();
		assertEquals(1, component.getTypeParameters().size());
		assertEquals(JavaClassScope.class, component.getTypeParameters().get(0).getClass());
		assertEquals("java.lang.Integer[]", component.getTypeParameters().get(0).getResolvedClassName());
	}
	
	public void testGetGenericInterfaces() throws IOException, ParseException {
		assertNull(JavaClassDescriptorUtils.getClassDescriptor(MOCK5).getCompilationUnit().getGenericInterfaces());
		assertNull(JavaClassDescriptorUtils.getClassDescriptor(MOCK4).getCompilationUnit().getGenericInterfaces());
		
		JavaClassDescriptor clazz = JavaClassDescriptorUtils.getClassDescriptor(MOCK6);
		assertNotNull(clazz.getCompilationUnit().getGenericInterfaces());
		assertEquals(1, clazz.getCompilationUnit().getGenericInterfaces().size());
		JavaTypeScope i1 = clazz.getCompilationUnit().getGenericInterfaces().get(0);
		assertNotNull(i1);
		assertTrue(i1 instanceof JavaParameterizedGenericTypeScope);
		JavaParameterizedGenericTypeScope param = (JavaParameterizedGenericTypeScope)i1;
		assertEquals(2, param.getTypeParameters().size());
		
		assertEquals(JavaClassScope.class, param.getTypeParameters().get(0).getClass());
		assertEquals("java.lang.String", param.getTypeParameters().get(0).getResolvedClassName());
		
		assertEquals(JavaClassScope.class, param.getTypeParameters().get(1).getClass());
		assertEquals("java.lang.Integer", param.getTypeParameters().get(1).getResolvedClassName());
	}
}
